package elephant.servers.webserver.core;

import java.io.ByteArrayInputStream;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import elephant.util.IOUtil;

/**
 * 
 * @author icecooly
 *
 */
public class Request {
	private static Logger logger =LoggerFactory.getLogger(Request.class);
	private static final String USER_AGENT = "user-agent";
	private List<String> querys;
	private HttpServletRequest servletRequest;
	private HttpSession session = null;
	private String body = null;
	private byte[] bodyAsBytes = null;
	private Set<String> headers = null;
	/**
	*/
	public Request(HttpServletRequest request) {
		this.servletRequest=request;
		String ss[]=request.getRequestURI().split("/");
		querys=new ArrayList<String>();
		for(int i=2;i<ss.length;i++){
			querys.add(ss[i]);
		}
	}
	//
	public List<String>querys(){
		return querys;
	}
	//
	/**
	 * @return request method e.g. GET, POST, PUT, ...
	 */
	public String requestMethod() {
		return servletRequest.getMethod();
	}

	/**
	 * @return the scheme
	 */
	public String scheme() {
		return servletRequest.getScheme();
	}

	/**
	 * @return the host
	 */
	public String host() {
		return servletRequest.getHeader("host");
	}

	/**
	 * @return the user-agent
	 */
	public String userAgent() {
		return servletRequest.getHeader(USER_AGENT);
	}

	/**
	 * @return the server port
	 */
	public int port() {
		return servletRequest.getServerPort();
	}

	/**
	 * @return the path info Example return: "/example/foo"
	 */
	public String pathInfo() {
		return servletRequest.getPathInfo();
	}

	/**
	 * @return the servlet path
	 */
	public String servletPath() {
		return servletRequest.getServletPath();
	}

	/**
	 * @return the context path
	 */
	public String contextPath() {
		return servletRequest.getContextPath();
	}

	/**
	 * @return the URL string
	 */
	public String url() {
		return servletRequest.getRequestURL().toString();
	}

	/**
	 * @return the content type of the body
	 */
	public String contentType() {
		return servletRequest.getContentType();
	}

	/**
	 * @return the client's IP address
	 */
	public String ip() {
		if (servletRequest == null){
	        return null;
		}
		try {
			String s = servletRequest.getHeader("X-Forwarded-For");
			if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)){
		        s = servletRequest.getHeader("Proxy-Client-IP");
		    }
		    if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)){
		        s = servletRequest.getHeader("WL-Proxy-Client-IP");
		    }
		    if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)){
		        s = servletRequest.getHeader("HTTP_CLIENT_IP");
		    }
		    if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)){
		        s = servletRequest.getHeader("HTTP_X_FORWARDED_FOR");
		    }
		    if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)){
		        s = servletRequest.getRemoteAddr();
		    }
		    if ("127.0.0.1".equals(s) || "0:0:0:0:0:0:0:1".equals(s)){   
		        s = InetAddress.getLocalHost().getHostAddress();
		    }
		    return s;
		} catch (Exception e) {
			logger.warn(e.getMessage(), e);
		}
		return "UNKNOWN IP";
	}

	/**
	 * @return the request body sent by the client
	 */
	public String body() {
		if (body == null) {
			readBody();
		}
		return body;
	}

	public byte[] bodyAsBytes() {
		if (bodyAsBytes == null) {
			readBody();
		}
		return bodyAsBytes;
	}

	private void readBody() {
		try {
			bodyAsBytes = IOUtil.toByteArray(servletRequest.getInputStream());
			body = IOUtil.toString(new ByteArrayInputStream(bodyAsBytes));
		} catch (Exception e) {
			logger.warn("Exception when reading body", e);
		}
	}

	/**
	 * @return the length of request.body
	 */
	public int contentLength() {
		return servletRequest.getContentLength();
	}

	/**
	 * gets the query param
	 *
	 * @param queryParam
	 *            the query parameter
	 * @return the value of the provided queryParam Example: query parameter
	 *         'id' from the following request URI: /hello?id=foo
	 */
	public String queryParams(String queryParam) {
		return servletRequest.getParameter(queryParam);
	}

	/**
	 * Gets the value for the provided header
	 *
	 * @param header
	 *            the header
	 * @return the value of the provided header
	 */
	public String getHeader(String headerName) {
		return servletRequest.getHeader(headerName);
	}

	/**
	 * @return all query parameters
	 */
	public Set<String> queryParams() {
		return servletRequest.getParameterMap().keySet();
	}

	/**
	 * @return all headers
	 */
	public Set<String> headers() {
		if (headers == null) {
			headers = new TreeSet<String>();
			Enumeration<String> enumeration = servletRequest.getHeaderNames();
			while (enumeration.hasMoreElements()) {
				headers.add(enumeration.nextElement());
			}
		}
		return headers;
	}
	//
	public Map<String,String> getHeaders(){
		Map<String, String> map = new HashMap<String, String>();
		Set<String> headerNames = headers();
		headerNames.forEach(headerName->{
			map.put(headerName,servletRequest.getHeader(headerName));
		});
		return map;
	}
	//
	public String queryURI(){
		return servletRequest.getRequestURI();
	}
	//
	/**
	 * @return the query string
	 */
	public String queryString() {
		return servletRequest.getQueryString();
	}

	/**
	 * Sets an attribute on the request (can be fetched in filters/routes later
	 * in the chain)
	 *
	 * @param attribute
	 *            The attribute
	 * @param value
	 *            The attribute value
	 */
	public void attribute(String attribute, Object value) {
		servletRequest.setAttribute(attribute, value);
	}

	/**
	 * Gets the value of the provided attribute
	 *
	 * @param attribute
	 *            The attribute value or null if not present
	 * @return the value for the provided attribute
	 */
	public Object attribute(String attribute) {
		return servletRequest.getAttribute(attribute);
	}

	/**
	 * @return all attributes
	 */
	public Set<String> attributes() {
		Set<String> attrList = new HashSet<String>();
		Enumeration<String> attributes = (Enumeration<String>) servletRequest
				.getAttributeNames();
		while (attributes.hasMoreElements()) {
			attrList.add(attributes.nextElement());
		}
		return attrList;
	}

	/**
	 * @return the raw HttpServletRequest object handed in by Jetty
	 */
	public HttpServletRequest getServletRequest() {
		return servletRequest;
	}



	/**
	 * Returns the current session associated with this request, or if the
	 * request does not have a session, creates one.
	 * @return the session associated with this request
	 */
	public HttpSession session() {
		if (session == null) {
			session=servletRequest.getSession();
		}
		return session;
	}

	/**
	 * Returns the current session associated with this request, or if there is
	 * no current session and <code>create</code> is true, returns a new
	 * session.
	 *
	 * @param create
	 *            <code>true</code> to create a new session for this request if
	 *            necessary; <code>false</code> to return null if there's no
	 *            current session
	 * @return the session associated with this request or <code>null</code> if
	 *         <code>create</code> is <code>false</code> and the request has no
	 *         valid session
	 */
	public HttpSession session(boolean create) {
		if (session == null) {
			session = servletRequest.getSession(create);
		}
		return session;
	}

	/**
	 * @return request cookies (or empty Map if cookies dosn't present)
	 */
	public Map<String, String> cookies() {
		Map<String, String> result = new HashMap<String, String>();
		Cookie[] cookies = servletRequest.getCookies();
		if (cookies != null) {
			for (Cookie cookie : cookies) {
				result.put(cookie.getName(), cookie.getValue());
			}
		}
		return result;
	}

	/**
	 * Gets cookie by name.
	 *
	 * @param name
	 *            name of the cookie
	 * @return cookie value or null if the cookie was not found
	 */
	public String cookie(String name) {
		Cookie[] cookies = servletRequest.getCookies();
		if (cookies != null) {
			for (Cookie cookie : cookies) {
				if (cookie.getName().equals(name)) {
					return cookie.getValue();
				}
			}
		}
		return null;
	}

	/**
	 * @return the part of this request's URL from the protocol name up to the
	 *         query string in the first line of the HTTP request.
	 */
	public String uri() {
		return servletRequest.getRequestURI();
	}

	/**
	 * @return Returns the name and version of the protocol the request uses
	 */
	public String protocol() {
		return servletRequest.getProtocol();
	}
	//
	//
	@Override
	public String toString() {
		StringBuilder sb=new StringBuilder();
		sb.append("\n>>uri:").append(uri()+"\n");
		servletRequest.getParameterMap().forEach((s,vv)->{
			sb.append(s).append(Arrays.toString(vv)+"\n");
		});
		String body=body();
		if(body!=null&&body.length()>0){
			sb.append("body:");
			if(body.length()>100){
				sb.append(body.substring(0, 100));
			}else{
				sb.append(body);
			}
		}
		return sb.toString();
	}
}
